/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Xceed.Silverlight.Data
{
  internal abstract class FetchPagingEnumerator<T> : PagingEnumerator<T>
  {
    #region Static Fields

    private static readonly Guid JumpMoveAndFetchCallId = DataUtils.CreateCallId();
    private static readonly Guid GetUnidirectionalPagesCallId = DataUtils.CreateCallId();
    private static readonly Guid GetBidirectionalPagesCallId = DataUtils.CreateCallId();
    private static readonly Guid GetBidirectionalReversedPagesCallId = DataUtils.CreateCallId();

    #endregion Static Fields

    protected FetchPagingEnumerator( IAsyncEnumerator childEnumerator )
      : base( childEnumerator )
    {
    }

    protected FetchPagingEnumerator( IAsyncEnumerator childEnumerator, Func<int> pageSize )
      : base( childEnumerator, pageSize )
    {
    }


    public override JumpMoveAndFetchAsyncResult BeginJumpMoveAndFetch(
      JumpMoveAndFetchParameters parameters,
      AsyncCallback callback,
      object state )
    {
      this.CheckMoveAndFetchParameters( parameters.ExpectedMoveCount, parameters.ExpectedFetchCount );

      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult(
                                                  FetchPagingEnumerator<T>.JumpMoveAndFetchCallId,
                                                  callback,
                                                  state,
                                                  parameters );

      if( !this.HandleUnidirectionalPages( asyncResult, parameters ) )
        if( !this.HandleBidirectionalPages( asyncResult, parameters ) )
          throw new DataInternalException();

      return asyncResult;
    }

    public override JumpMoveAndFetchResult<T> EndJumpMoveAndFetch(
      JumpMoveAndFetchAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( FetchPagingEnumerator<T>.JumpMoveAndFetchCallId );

      return asyncResult.GetCallResult<JumpMoveAndFetchResult<T>>();
    }

    protected virtual Page GetNextPage( 
      SourcePosition sourcePosition,
      long moveCount,
      int moveUnit, 
      int pageSize )
    {
      return new Page( sourcePosition, moveCount, 0 );
    }

    protected override void Dispose( bool disposing )
    {
      m_disposed = true;

      base.Dispose( disposing );
    }

    private JumpMoveAndFetchAsyncResult BeginGetUnidirectionalPages(
      JumpMoveAndFetchParameters parameters,
      AsyncCallback callback,
      object state )
    {
      long itemsCountToSkip = parameters.ExpectedMoveCount;
      int itemsCountToTake = parameters.ExpectedFetchCount;

      // Make sure the query is unidirectional.
      Debug.Assert( ( itemsCountToSkip == 0 )
                 || ( itemsCountToTake == 0 )
                 || ( DataUtils.IsQueryingForward( itemsCountToSkip, itemsCountToTake ) == DataUtils.IsQueryingForward( 0, itemsCountToTake ) ) );

      SourcePosition sourcePosition = parameters.SourcePosition;
      List<T> currentItems = new List<T>();
      Nullable<long> actualMoveCount = null;
      SourcePosition endPosition = null;
      RelativeOffset endPositionOffset = null;
      EstimateInt64 totalItemsCount = null;
      bool isFirstPage = true;
      int unit = ( itemsCountToSkip != 0 ) ? Math.Sign( itemsCountToSkip ) : Math.Sign( itemsCountToTake );

      // This anonymous method starts the query that will get a data page.
      Func<AsyncCallback, object, IAsyncResult> getPage = null;
      getPage =
        ( cb, st ) =>
        {
          long moveCount = 0;

          // It takes one move to get in bound.
          if( itemsCountToSkip != 0 && sourcePosition.IsOutOfBound )
          {
            moveCount = unit;
            itemsCountToSkip -= unit;
          }

          long itemsCount = itemsCountToSkip + itemsCountToTake;
          // Try to fetch an extra item to get an end position after
          // we've skip all items or to get over an item we have 
          // already fetched.
          if( ( itemsCountToSkip != 0 && itemsCountToTake == 0 )
            || ( itemsCountToSkip == 0 && itemsCountToTake == unit && sourcePosition is ItemPosition ) )
          {
            itemsCount += unit;
          }

          int pageSize = this.GetFetchCount( itemsCount );

          // Get the starting position of the next page.
          if( !isFirstPage )
          {
            Page nextPage = this.GetNextPage(
                              sourcePosition,
                              moveCount,
                              unit,
                              pageSize );

            sourcePosition = nextPage.SourcePosition;
            moveCount = nextPage.MoveCount;

            if( itemsCountToSkip != 0 )
            {
              long implicitMoveCount = nextPage.ImplicitMoveCount;
              if( implicitMoveCount != 0 )
              {
                itemsCountToSkip -= implicitMoveCount;

                if( actualMoveCount.HasValue )
                {
                  actualMoveCount = actualMoveCount.Value + implicitMoveCount;
                }
              }
            }
          }

          // Create the parameters to get the page.
          JumpMoveAndFetchParameters pageParameters = new JumpMoveAndFetchParameters(
                                                        sourcePosition,
                                                        moveCount,
                                                        pageSize,
                                                        parameters.ApproximateMove );

          return base.BeginJumpMoveAndFetch( pageParameters, cb, st );
        };

      // This anonymous method retrieve the data page and merge the result.
      Func<AsyncResult, IAsyncResult, DependentCallResult> mergePage = null;
      mergePage =
        ( par, iar ) =>
        {
          JumpMoveAndFetchResult<T> pageResult = base.EndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )iar );

          if( m_disposed )
            return new DependentCallResult( null );

          IEnumerable<T> fetchedItems = pageResult.FetchedItems;

          // Set the actual move count.
          if( isFirstPage )
          {
            actualMoveCount = pageResult.ActualMoveCount;
          }

          // Set/Refresh the total items count.
          if( pageResult.TotalCount != null )
          {
            totalItemsCount = pageResult.TotalCount;
          }
          // The total items count we had previously is unreliable.
          else if( totalItemsCount != null )
          {
            totalItemsCount = totalItemsCount.ToEstimate();
          }

          if( fetchedItems != null )
          {
            int maxSkipCountAllowed = fetchedItems.Count() - ( ( pageResult.Result ) ? 1 : 0 );
            maxSkipCountAllowed = Math.Max( 0, maxSkipCountAllowed );

            // Skip the items that aren't in the final result.
            int maxSkipCount = Convert.ToInt32( Math.Min( maxSkipCountAllowed, Math.Abs( itemsCountToSkip ) ) );
            if( maxSkipCount > 0 )
            {
              int skipCount = maxSkipCount * unit;

              // Adjust the actual move count.
              if( actualMoveCount.HasValue )
              {
                actualMoveCount = actualMoveCount.Value + skipCount;
              }

              fetchedItems = fetchedItems.Skip( maxSkipCount );
              itemsCountToSkip -= skipCount;
            }

            // Take the items that are in the final result.
            if( itemsCountToSkip == 0 && fetchedItems.Any() )
            {
              // We must skip the starting item if we have already taken it last page.
              if( !isFirstPage && sourcePosition is ItemPosition )
              {
                if( object.Equals( fetchedItems.First(), ( ( ItemPosition )sourcePosition ).Item ) )
                {
                  fetchedItems = fetchedItems.Skip( 1 );
                }
              }

              int maxTakeCount = Math.Max( 0, Math.Min( fetchedItems.Count(), Math.Abs( itemsCountToTake ) ) );
              if( maxTakeCount > 0 )
              {
                currentItems.AddRange( fetchedItems.Take( maxTakeCount ) );

                fetchedItems = fetchedItems.Skip( maxTakeCount );
                itemsCountToTake -= maxTakeCount * unit;
              }
            }
          }

          // We do not need to fetch any more item since we've met the end of the source
          // or we have fetched every requested item.
          bool success = pageResult.Result;
          bool fetchCompleted = ( itemsCountToSkip == 0 && itemsCountToTake == 0 );
          if( !success || fetchCompleted )
          {
            endPosition = pageResult.EndPositionObject;
            endPositionOffset = pageResult.EndPositionOffset;

            if( fetchCompleted )
            {
              bool hasFetchedTooMuch = ( fetchedItems != null && fetchedItems.Any() );
              bool isExtraItemOutOfBound = ( !success )
                                        && ( fetchedItems != null )
                                        && ( currentItems.Count > 0 )
                                        && ( !hasFetchedTooMuch );

              // We have fetched more items than necessary.  The end position
              // we have now isn't the one we would have if we weren't emulating
              // the move with a fetch.
              if( hasFetchedTooMuch || isExtraItemOutOfBound )
              {
                success = true;

                T endItem;
                int offset;

                if( currentItems.Count > 0 )
                {
                  endItem = currentItems.Last();
                  offset = ( !isExtraItemOutOfBound )
                             ? fetchedItems.Count() * unit
                             : unit;
                }
                else
                {
                  endItem = fetchedItems.First();
                  offset = ( !isExtraItemOutOfBound )
                             ? ( fetchedItems.Count() - 1 ) * unit
                             : unit;
                }

                endPosition = new ItemPosition( endItem );

                // Adjust the end position offset.
                if( endPositionOffset != null )
                {
                  if( endPositionOffset.ReferencePosition == ReferencePoint.BeforeFirst )
                  {
                    endPositionOffset -= offset;
                  }
                  else
                  {
                    endPositionOffset += offset;
                  }
                }
              }
            }

            ICollection<T> requestedItems = ( success || currentItems.Count > 0 )
                                              ? currentItems
                                              : null;

            return new DependentCallResult(
                     new JumpMoveAndFetchResult<T>(
                       ( JumpMoveAndFetchAsyncResult )par,
                       actualMoveCount,
                       requestedItems,
                       success,
                       endPosition,
                       endPositionOffset,
                       totalItemsCount ) );
          }
          else
          {
            // We need to fetch another page.
            sourcePosition = pageResult.EndPositionObject;
            isFirstPage = false;

            return new DependentCallResult( null, getPage, mergePage );
          }
        };

      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult(
                                                  FetchPagingEnumerator<T>.GetUnidirectionalPagesCallId,
                                                  callback,
                                                  state,
                                                  parameters );

      asyncResult.Start( getPage, mergePage );

      return asyncResult;
    }

    private DependentCallResult EndGetUnidirectionalPages(
      JumpMoveAndFetchAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( FetchPagingEnumerator<T>.GetUnidirectionalPagesCallId );

      return new DependentCallResult( asyncResult.GetCallResult<JumpMoveAndFetchResult<T>>() );
    }

    private bool HandleUnidirectionalPages(
      JumpMoveAndFetchAsyncResult asyncResult,
      JumpMoveAndFetchParameters parameters )
    {
      long moveCount = parameters.ExpectedMoveCount;
      int fetchCount = parameters.ExpectedFetchCount;

      // Make sure the query is unidirectional.
      if( moveCount != 0
        && fetchCount != 0
        && DataUtils.IsQueryingForward( moveCount, fetchCount ) != DataUtils.IsQueryingForward( 0, fetchCount ) )
        return false;

      // This anonymous method will fetch a data page.
      Func<AsyncCallback, object, IAsyncResult> getPages =
        ( cb, st ) => this.BeginGetUnidirectionalPages( parameters, cb, st );

      // This anonymous method will ask for more pages if necessary.
      Func<AsyncResult, IAsyncResult, DependentCallResult> mergePages =
        ( par, iar ) => this.EndGetUnidirectionalPages( ( JumpMoveAndFetchAsyncResult )iar );

      asyncResult.Start( getPages, mergePages );

      return true;
    }

    private JumpMoveAndFetchAsyncResult BeginGetBidirectionalPages(
      JumpMoveAndFetchParameters parameters,
      AsyncCallback callback,
      object state )
    {
      long itemsCountToSkip = parameters.ExpectedMoveCount;
      int itemsCountToTake = parameters.ExpectedFetchCount;

      // Make sure the query is bidirectional.
      Debug.Assert( ( itemsCountToSkip != 0 )
                 && ( itemsCountToTake != 0 )
                 && ( DataUtils.IsQueryingForward( itemsCountToSkip, itemsCountToTake ) != DataUtils.IsQueryingForward( 0, itemsCountToTake ) ) );

      // This anonymous method starts the query that is returning the items that
      // are in the move direction from the starting item.
      Func<AsyncCallback, object, IAsyncResult> getItems =
        ( cb, st ) =>
        {
          JumpMoveAndFetchParameters newParameters = new JumpMoveAndFetchParameters(
                                                       parameters.SourcePosition,
                                                       itemsCountToSkip,
                                                       Convert.ToInt32( -itemsCountToSkip ),
                                                       parameters.ApproximateMove );

          return this.BeginJumpMoveAndFetch( newParameters, cb, st );
        };

      // This anonymous method starts the query that is returning the items that
      // are in the fetch direction from the starting item.
      Func<AsyncResult, IAsyncResult, DependentCallResult> getRemainingItems =
        ( par, iar ) =>
        {
          JumpMoveAndFetchResult<T> moveResult = this.EndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )iar );

          if( m_disposed )
            return new DependentCallResult( null );

          if( !moveResult.Result )
            return new DependentCallResult(
                     new JumpMoveAndFetchResult<T>(
                       ( JumpMoveAndFetchAsyncResult )par,
                       moveResult.ActualMoveCount,
                       moveResult.FetchedItems,
                       moveResult.Result,
                       moveResult.EndPositionObject,
                       moveResult.EndPositionOffset,
                       moveResult.TotalCount ) );

          Func<AsyncCallback, object, IAsyncResult> getReverseItems =
            ( cb, st ) =>
            {
              JumpMoveAndFetchParameters newParameters = new JumpMoveAndFetchParameters(
                                                           parameters.SourcePosition,
                                                           0,
                                                           Convert.ToInt32( itemsCountToSkip + itemsCountToTake ),
                                                           parameters.ApproximateMove );

              return this.BeginJumpMoveAndFetch( newParameters, cb, st );
            };

          Func<AsyncResult, IAsyncResult, DependentCallResult> mergeItems =
            ( p, ar ) =>
            {
              JumpMoveAndFetchResult<T> fetchResult = this.EndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )ar );

              if( m_disposed )
                return new DependentCallResult( null );

              List<T> currentItems = new List<T>( moveResult.FetchedItems );
              if( fetchResult.FetchedItems != null )
              {
                currentItems.AddRange( fetchResult.FetchedItems );
              }

              return new DependentCallResult(
                       new JumpMoveAndFetchResult<T>(
                         ( JumpMoveAndFetchAsyncResult )p,
                         moveResult.ActualMoveCount,
                         currentItems,
                         fetchResult.Result,
                         fetchResult.EndPositionObject,
                         fetchResult.EndPositionOffset,
                         fetchResult.TotalCount ) );
            };

          return new DependentCallResult( null, getReverseItems, mergeItems );
        };

      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult(
                                                  FetchPagingEnumerator<T>.GetBidirectionalPagesCallId,
                                                  callback,
                                                  state,
                                                  parameters );

      asyncResult.Start( getItems, getRemainingItems );

      return asyncResult;
    }

    private DependentCallResult EndGetBidirectionalPages(
      JumpMoveAndFetchAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( FetchPagingEnumerator<T>.GetBidirectionalPagesCallId );

      return new DependentCallResult( asyncResult.GetCallResult<JumpMoveAndFetchResult<T>>() );
    }

    private bool HandleBidirectionalPages(
      JumpMoveAndFetchAsyncResult asyncResult,
      JumpMoveAndFetchParameters parameters )
    {
      long moveCount = parameters.ExpectedMoveCount;
      int fetchCount = parameters.ExpectedFetchCount;

      // Make sure the query is bidirectional.
      if( moveCount == 0
        || fetchCount == 0
        || DataUtils.IsQueryingForward( moveCount, fetchCount ) == DataUtils.IsQueryingForward( 0, fetchCount ) )
        return false;

      // Try to apply an optimized version of a bidirectional query.
      if( this.HandleBidirectionalReversedPages( asyncResult, parameters ) )
        return true;

      // This anonymous method will fetch a data page.
      Func<AsyncCallback, object, IAsyncResult> getPages =
        ( cb, st ) => this.BeginGetBidirectionalPages( parameters, cb, st );

      // This anonymous method will ask for more pages if necessary.
      Func<AsyncResult, IAsyncResult, DependentCallResult> mergePages =
        ( par, iar ) => this.EndGetBidirectionalPages( ( JumpMoveAndFetchAsyncResult )iar );

      asyncResult.Start( getPages, mergePages );

      return true;
    }

    private JumpMoveAndFetchAsyncResult BeginGetBidirectionalReversedPages(
      JumpMoveAndFetchParameters parameters,
      AsyncCallback callback,
      object state )
    {
      long itemsCountToSkip = parameters.ExpectedMoveCount;
      int itemsCountToTake = parameters.ExpectedFetchCount;

      // Make sure the query is an optimizable bidirectional query.
      Debug.Assert( ( itemsCountToSkip != 0 )
                 && ( itemsCountToTake != 0 )
                 && ( DataUtils.IsQueryingForward( itemsCountToSkip, itemsCountToTake ) != DataUtils.IsQueryingForward( 0, itemsCountToTake ) )
                 && ( Math.Abs( itemsCountToSkip ) >= Math.Abs( itemsCountToTake ) - 1 ) );

      SourcePosition sourcePosition = parameters.SourcePosition;

      // Transform the query into a unidirectional query.
      itemsCountToSkip += itemsCountToTake + DataUtils.GetUnit( itemsCountToSkip );
      itemsCountToTake = -itemsCountToTake;

      bool failFromStart = false;
      SourcePosition endPosition = null;
      RelativeOffset endPositionOffset = null;

      // Since we are starting out of bound, we already know one item will be missing.
      if( ( itemsCountToSkip == 0 ) && ( sourcePosition.IsOutOfBound ) )
      {
        failFromStart = true;
        endPosition = sourcePosition;
        endPositionOffset = new RelativeOffset(
                              0,
                              ( sourcePosition.IsBeforeFirst )
                                ? ReferencePoint.BeforeFirst
                                : ReferencePoint.AfterLast );

        int unit = DataUtils.GetUnit( itemsCountToTake );
        itemsCountToSkip = unit;
        itemsCountToTake = itemsCountToTake - unit;
      }

      // This anonymous method starts the unidirectional query.
      Func<AsyncCallback, object, IAsyncResult> getData =
        ( cb, st ) =>
        {
          JumpMoveAndFetchParameters newParameters = new JumpMoveAndFetchParameters(
                                                       sourcePosition,
                                                       itemsCountToSkip,
                                                       itemsCountToTake,
                                                       parameters.ApproximateMove );

          return this.BeginJumpMoveAndFetch( newParameters, cb, st );
        };

      // This anonymous method reverse the result set.
      Func<AsyncResult, IAsyncResult, DependentCallResult> reverseData =
        ( par, iar ) =>
        {
          JumpMoveAndFetchResult<T> result = this.EndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )iar );

          if( m_disposed )
            return new DependentCallResult( null );

          bool success = !failFromStart && result.Result;
          Nullable<long> actualMoveCount = result.ActualMoveCount;
          ICollection<T> fetchedItems = result.FetchedItems;
          EstimateInt64 totalItemsCount = result.TotalCount;

          // We were able to move to the appropriate location.
          if( result.Result )
          {
            // Reverse the fetched items.
            List<T> currentItems = new List<T>( fetchedItems.Reverse() );
            fetchedItems = currentItems;

            // Find out the real actual move count.
            if( actualMoveCount.HasValue )
            {
              actualMoveCount = actualMoveCount.Value + result.GetFetchDisplacement();
            }

            if( !failFromStart )
            {
              endPosition = result.EndPositionObject;
              endPositionOffset = result.EndPositionOffset;

              // Find out the real end position.
              if( currentItems.Count > 1 )
              {
                endPosition = new ItemPosition( currentItems.Last() );

                if( endPositionOffset != null )
                {
                  long offset = ( currentItems.Count - 1 ) * -result.GetFetchUnit();

                  if( endPositionOffset.ReferencePosition == ReferencePoint.BeforeFirst )
                  {
                    endPositionOffset += offset;
                  }
                  else
                  {
                    endPositionOffset -= offset;
                  }
                }
              }
            }
          }
          // The move went beyond the end of the source.
          else
          {
            // Find out the real actual move count.
            if( actualMoveCount.HasValue && fetchedItems != null )
            {
              int extraMoveCount = fetchedItems.Count() * result.GetFetchUnit();

              actualMoveCount = actualMoveCount.Value + extraMoveCount;
            }

            // Find out if we are before or after the end of source.
            if( result.ExpectedFetchCount > 0 )
            {
              endPosition = SourcePosition.AfterLast;
              endPositionOffset = new RelativeOffset( 0, ReferencePoint.AfterLast );
            }
            else
            {
              endPosition = SourcePosition.BeforeFirst;
              endPositionOffset = new RelativeOffset( 0, ReferencePoint.BeforeFirst );
            }

            // Since we went beyond the end of the source, the fetch part shouldn't have
            // occured.  We must not return any items.
            fetchedItems = null;
          }

          return new DependentCallResult(
                   new JumpMoveAndFetchResult<T>(
                     ( JumpMoveAndFetchAsyncResult )par,
                     actualMoveCount,
                     fetchedItems,
                     success,
                     endPosition,
                     endPositionOffset,
                     totalItemsCount ) );
        };

      JumpMoveAndFetchAsyncResult asyncResult = new JumpMoveAndFetchAsyncResult(
                                                  FetchPagingEnumerator<T>.GetBidirectionalReversedPagesCallId,
                                                  callback,
                                                  state,
                                                  parameters );

      asyncResult.Start( getData, reverseData );

      return asyncResult;
    }

    private DependentCallResult EndGetBidirectionalReversedPages(
      JumpMoveAndFetchAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( FetchPagingEnumerator<T>.GetBidirectionalReversedPagesCallId );

      return new DependentCallResult( asyncResult.GetCallResult<JumpMoveAndFetchResult<T>>() );
    }

    private bool HandleBidirectionalReversedPages(
      JumpMoveAndFetchAsyncResult asyncResult,
      JumpMoveAndFetchParameters parameters )
    {
      long moveCount = parameters.ExpectedMoveCount;
      int fetchCount = parameters.ExpectedFetchCount;

      // Make sure the query is bidirectional, but can be emulated as unidirectional.
      if( moveCount == 0
        || fetchCount == 0
        || DataUtils.IsQueryingForward( moveCount, fetchCount ) == DataUtils.IsQueryingForward( 0, fetchCount )
        || Math.Abs( moveCount ) < Math.Abs( fetchCount ) - 1 )
        return false;

      // This anonymous method will fetch a data page.
      Func<AsyncCallback, object, IAsyncResult> getPages =
        ( cb, st ) => this.BeginGetBidirectionalReversedPages( parameters, cb, st );

      // This anonymous method will ask for more pages if necessary.
      Func<AsyncResult, IAsyncResult, DependentCallResult> mergePages =
        ( par, iar ) => this.EndGetBidirectionalReversedPages( ( JumpMoveAndFetchAsyncResult )iar );

      asyncResult.Start( getPages, mergePages );

      return true;
    }


    #region Private Fields

    private bool m_disposed; //false

    #endregion


    #region Protected Page Nested Type

    protected struct Page
    {
      internal Page( SourcePosition sourcePosition, long moveCount, long implicitMoveCount )
      {
        if( sourcePosition == null )
          throw new ArgumentNullException( "sourcePosition" );

        this.SourcePosition = sourcePosition;
        this.MoveCount = moveCount;
        this.ImplicitMoveCount = implicitMoveCount;
      }

      internal readonly SourcePosition SourcePosition;
      internal readonly long MoveCount;
      internal readonly long ImplicitMoveCount;
    }

    #endregion
  }
}
